-
-
Notifications
You must be signed in to change notification settings - Fork 989
Improve msp send #4510
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Improve msp send #4510
Conversation
WalkthroughReplaces MSP adaptive timeout/flags with a fixed TIMEOUT and simplifies send_message; adds top-level dynamic import bootstrapping MSP debug tools; treats test environments as compatible in browser checks; bumps one test API version; and introduces a new MSP debug suite (README, barrel, dashboard, queue monitor, stress tester, test runner, tools). Changes
Sequence Diagram(s)MSP send_message with fixed TIMEOUTsequenceDiagram
participant Caller
participant MSP
participant Serial
participant Timer
Caller->>MSP: send_message(code, data, callback...)
MSP->>MSP: if (!serial.connected) -> callback_msp early return
MSP->>MSP: check duplicate via callbacks.some(...)
alt send permitted (no duplicate or data present)
MSP->>MSP: create request {code, callback, start: performance.now(), ...}
MSP->>Timer: setTimeout(..., TIMEOUT)
MSP->>Serial: serial.send(bufferOut, cb)
Serial-->>MSP: send callback -> set obj.stop, compute executionTime, call callback_sent if bytes match
Timer-->>MSP: timeout fires -> if executionTime>5000ms log long-exec warning, clear timer, invoke timeout handling
else duplicate and no data
MSP->>Caller: skip send, resolve via callback_msp
end
Dynamic loading of MSP Debug Tools (high-level)sequenceDiagram
participant App as Main App
participant Import as Dynamic Import
participant Tools as MSP Debug Tools
participant Dashboard as mspDebugDashboard
participant Monitor as mspQueueMonitor
participant Stress as mspStressTest
App->>Import: import('./src/js/msp/debug/msp_debug_tools.js')
Import-->>Tools: load modules, print console banner
alt dev host (localhost/127.0.0.1)
Tools->>Monitor: dynamic import and startMonitoring(2000)
Tools->>App: console guidance messages
end
Tools->>Dashboard: initialize UI (on demand)
Dashboard->>Monitor: subscribe for live updates
Dashboard->>Stress: trigger stress tests
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
src/js/msp.js (1)
402-413
:⚠️ Potential issue
_validateSendMessage
ignoresdoCallbackOnError
flagError-side invocation of
callback_msp
is unconditional. That breaks the original contract implied by thedoCallbackOnError
parameter and can trigger “double callbacks” or resolve aPromise
that expected an actual response.-_validateSendMessage(code, callback_msp) { +_validateSendMessage(code, callback_msp, doCallbackOnError = false) { … - if (callback_msp) { + if (doCallbackOnError && callback_msp) { callback_msp(); }You’ll have to forward the flag from
send_message
.
🧹 Nitpick comments (2)
src/js/msp.js (2)
423-431
: Unused field –callbackOnError
is never referenced
_createRequestObject
storescallbackOnError
, but no subsequent code reads it after the refactor. Either remove the property or reinstate the error-handling path.Eliminating dead fields simplifies debugging and reduces cognitive load.
457-467
: Aggressive timeout decay may under-shootMIN_TIMEOUT
this.timeout--
happens on every send, regardless of frequency, and can push the value belowMIN_TIMEOUT
whentimeout
has already been clamped lower elsewhere.-if (this.timeout > this.MIN_TIMEOUT) { - this.timeout--; -} +this.timeout = Math.max(this.MIN_TIMEOUT, this.timeout - 1);A simple guard prevents negative or sub-minimum timeouts.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/js/msp.js
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (2)
src/js/serial.js (2)
serial
(364-364)serial
(364-364)src/js/gui.js (1)
GUI
(484-484)
🔇 Additional comments (1)
src/js/msp.js (1)
415-418
: Duplicate detection is too coarse
_isDuplicateRequest
only checks equality ofcode
; it ignores the payload (data
).
Two requests with the same MSP code but different parameters will be treated as duplicates and the second payload may never be transmitted.Consider comparing a
(code, dataHash)
tuple or always sending whendataLength > 0
(even if duplicate).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/js/msp.js (3)
390-398
: Unused allocation for duplicates
requestObj
is instantiated even when the call is considered a duplicate and then discarded. This happens on every duplicate invocation and can become a noticeable GC hot-spot under heavy traffic.Move the allocation inside the
if (!isDuplicateRequest)
block so it is only created when needed.
445-456
: Timer remains referenced after giving up – clear it to aid GCWhen
MAX_RETRIES
is exceeded the function bails out but does notclearTimeout(requestObj.timer);
.
While the timer has already fired, keeping the handle prevents some JS engines from reclaiming it promptly.if (requestObj.attempts >= this.MAX_RETRIES) { console.error(`MSP: Request ${requestObj.code} exceeded max retries (${this.MAX_RETRIES}), giving up`); - // Remove from callbacks to prevent memory leak + clearTimeout(requestObj.timer); // 💡 explicit cleanup + // Remove from callbacks to prevent memory leak this._removeRequestFromCallbacks(requestObj);
411-413
: Adaptive back-off step might starve high-latency links
this.timeout
is decreased by a fixed 5 ms on every successful send. On slow links (e.g. Bluetooth) this can quickly shrink below the real RTT, leading to unnecessary retries. Consider decaying by a percentage (e.g.timeout *= 0.9
) or pinning a lower bound based on recent RTT measurements instead of a constant 5 ms.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/js/msp.js
(3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (3)
src/js/serial.js (2)
serial
(364-364)serial
(364-364)src/js/data_storage.js (1)
CONFIGURATOR
(7-40)src/js/gui.js (1)
GUI
(484-484)
src/js/msp.js
Outdated
}); | ||
}, this.timeout); | ||
// Track only the first outstanding request for a given code | ||
if (!isDuplicateRequest) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure if this is the reason for missing events? e.g. motor slider getting "stuck" on old values even if the slider got pulled all the way down ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - see next commit - not sure if enough - or different from master - at least seems more stable - no longer locking up on SPBEF7V3 for the moment as it does on master.
eb77f13
to
4fcad77
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/js/msp.js (1)
399-420
:⚠️ Potential issueDuplicate requests silently drop callbacks
When
isDuplicateRequest
istrue
, the newcallback_msp
is not stored anywhere, so callers issuing concurrent identical requests will never be notified.
This regresses previous behaviour and breaks APIs that rely on per-call completion.Quick remedy: attach additional callbacks to the existing request object instead of discarding them.
-const isDuplicateRequest = this.callbacks.some((instance) => instance.requestKey === requestKey); +const existingReq = this.callbacks.find((i) => i.requestKey === requestKey); +const isDuplicateRequest = Boolean(existingReq); … -if (!isDuplicateRequest) { +if (!isDuplicateRequest) { this._setupTimeout(requestObj, bufferOut); this.callbacks.push(requestObj); +} else if (callback_msp) { + // fan-out: queue extra callback(s) on the first request + (existingReq.extraCallbacks ??= []).push(callback_msp); }And, when the response arrives, invoke
extraCallbacks
as well.
🧹 Nitpick comments (4)
src/js/msp.js (4)
376-389
: Hash function may collide & yield negative keys
_createRequestKey
uses a simple 32-bit rolling hash that can:
- Produce the same hash for different payloads (higher collision risk on large traffic).
- Return negative numbers, generating keys like
123:-842150451
which are harder to eyeball/debug.Consider switching to a safer deterministic scheme, e.g. CRC-32 or base64 of the first 4 bytes plus
length
, to lower collision odds and keep keys human-readable.
421-428
: Inefficient duplicate scan — O(N) each call
this.callbacks.some()
does a linear search every timesend_message
is invoked.
WithMAX_QUEUE_SIZE
already tracked, switching to aMap<requestKey, requestObj>
would give O(1) look-ups and simplify removal logic.
433-438
: Timer handle lost if_setupTimeout
is called twice
_setupTimeout
overwritesrequestObj.timer
without clearing a possibly existing one.
Although current flow prevents re-entrance, guarding against future changes is cheap:+if (requestObj.timer) { + clearTimeout(requestObj.timer); +} requestObj.timer = setTimeout(() => { this._handleTimeout(requestObj, bufferOut); }, this.TIMEOUT);
439-472
: Retry path never resetsstart
— metrics become meaningless
requestObj.start
is set once insend_message
but never updated on retries, so any later duration calculations accumulate total wall-time, not per-attempt latency.Either reset
start
inside_handleTimeout
before re-arming the timer or drop it altogether if no longer used.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/js/msp.js
(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/js/msp.js (2)
src/js/serial.js (2)
serial
(364-364)serial
(364-364)src/js/data_storage.js (1)
CONFIGURATOR
(7-40)
🔇 Additional comments (1)
src/js/msp.js (1)
69-73
:✅ Verification successful
Confirm removal of legacy
this.timeout
referencesYou have introduced a fixed
TIMEOUT
constant but removed the old adaptivethis.timeout
.
Please grep the rest of the file / project for stalethis.timeout
usages to avoid undefined-property bugs at runtime.
🏁 Script executed:
#!/bin/bash # Find any remaining references to the old timeout field rg --line-number $'this\\.timeout'Length of output: 665
No stale
this.timeout
references foundA full‐repo search for
this.timeout
only returns occurrences ofthis.timeout_array
insrc/js/gui.js
, not the legacythis.timeout
property. There are no remaining direct references to the removed adaptive timeout field—this concern can be closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/js/msp.js (1)
482-491
:_removeRequestFromCallbacks
leaves Map/key tracking unresolvedIf you adopt the
Map
optimisation above, ensure this method also deletes therequestKey
entry to keep the structures in sync.
🧹 Nitpick comments (4)
src/js/msp.js (4)
69-73
: Expose retry parameters instead of hard-coding them
MAX_RETRIES
,MAX_QUEUE_SIZE
, andTIMEOUT
are baked into the module, making it impossible to tune behaviour from the outside (e.g. per-board, per-environment, unit-tests). Consider reading them fromCONFIGURATOR
, a constructor argument, or at least exporting setters.
399-420
: Duplicate check performsO(n)
scans on every call
this.callbacks.some(...)
linearly scans the queue. Under heavy traffic (e.g. telemetry floods) the cost grows quadratically.Two easy wins:
- Keep a
Map<requestKey, requestObj>
to getO(1)
look-ups and removals.- Build
bufferOut
after the duplicate check to avoid unnecessary encoding work when the request is dropped.
433-438
: Timer double-cleared – harmless but needless
_handleTimeout
callsclearTimeout(requestObj.timer)
(l.464) and_removeRequestFromCallbacks
does the same. The second call is redundant and can be omitted to save a micro-op.
439-480
: Constant timeout may hammer dead linksRetries fire every fixed
TIMEOUT
ms. On an unplugged device this means 10 exact re-transmits per second, wasting bandwidth and log space.
An exponential back-off (e.g.TIMEOUT * 2 ** attempts
) curbs network noise and still converges quickly when the link is restored.
🛑 Comments failed to post (1)
src/js/msp.js (1)
376-389: 🛠️ Refactor suggestion
_createRequestKey
collision risk & signed-shift bugThe DJB-style hash on a
Uint8Array
will inevitably collide for different payloads and, because& 0xffffffff
yields a signed 32-bit value, the string key can even become negative.
A wrong “duplicate” classification silently drops tracking / retries.- let hash = 0; - for (const byte of data) { - hash = ((hash << 5) - hash + byte) & 0xffffffff; - } - return `${code}:${hash}`; + // Use a stable unsigned hash (FNV-1a) to minimise collisions + let hash = 2166136261 >>> 0; + for (const byte of data) { + hash ^= byte; + hash = Math.imul(hash, 16777619) >>> 0; + } + return `${code}:${hash.toString(16)}`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.// Helper function to create a unique key for request identification _createRequestKey(code, data) { if (!data || data.length === 0) { return `${code}:empty`; } // Use a stable unsigned hash (FNV-1a) to minimise collisions let hash = 2166136261 >>> 0; for (const byte of data) { hash ^= byte; hash = Math.imul(hash, 16777619) >>> 0; } return `${code}:${hash.toString(16)}`; },
🤖 Prompt for AI Agents
In src/js/msp.js around lines 376 to 389, the _createRequestKey function uses a DJB-style hash that can produce collisions and negative values due to signed 32-bit shifts. To fix this, replace the hash calculation with a more robust hashing method that avoids collisions and ensures the hash is always unsigned and positive. For example, use a standard hashing library or implement a non-signed hash function that produces a unique, non-negative key string for the given data.
src/js/msp.js
Outdated
} | ||
// Create unique key combining code and data | ||
const requestKey = this._createRequestKey(code, data); | ||
const isDuplicateRequest = this.callbacks.some((instance) => instance.requestKey === requestKey); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not return here if a dupe is detected ? saves your the conditions below. looks like the code is not following "one return per function" convention anyways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and, why not "rate-limit" the events from sliders at the source ? i bet there are different event types that need different handling anyways.... like those motor sliders, where probably first and last event is very important, but all events in between can be lost without any noticeable effect for the user, but then there are events that cannot be lost, like transmission of cli stuff ?
src/js/msp.js
Outdated
this._setupTimeout(requestObj, bufferOut); | ||
this.callbacks.push(requestObj); | ||
} | ||
|
||
this.callbacks.push(obj); | ||
|
||
// always send messages with data payload (even when there is a message already in the queue) | ||
if (data || !requestExists) { | ||
if (this.timeout > this.MIN_TIMEOUT) { | ||
this.timeout--; | ||
} | ||
|
||
// Send message if it has data or is a new request | ||
if (data || !isDuplicateRequest) { | ||
serial.send(bufferOut, (sendInfo) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(note the line range if this comment)
i would probably start the timeout and call send only if the queue is empty, or if the previous "send" succeeded?
otherwise the buffers of the serial protocols are immediately filled and can overflow
src/js/msp.js
Outdated
); | ||
|
||
// Check if max retries exceeded OR queue is too large | ||
if (requestObj.attempts >= this.MAX_RETRIES || this.callbacks.length > this.MAX_QUEUE_SIZE) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would perform the queue size check prior to insertion of the objects, in the send_message function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (4)
src/js/msp.js (4)
69-73
: Freeze or namespace retry-config constants to prevent accidental mutationDefining
MAX_RETRIES
,MAX_QUEUE_SIZE
andTIMEOUT
directly on the mutableMSP
object means any consumer can overwrite them at runtime:MSP.TIMEOUT = 1; // 💥If you really want them tweakable, expose an explicit setter.
Otherwise, lock them down:- MAX_RETRIES: 10, - MAX_QUEUE_SIZE: 50, - TIMEOUT: 1000, + MAX_RETRIES: 10, + MAX_QUEUE_SIZE: 50, + TIMEOUT: 1000, } + +Object.freeze(MSP); // or freeze only MSP.retryConfig sub-object
376-389
: Hashing scheme is fragile & collision-prone
_createRequestKey
builds a 32-bit additive hash; for large or similar payloads collisions are easy, producing false “duplicates”.
Additionallyhash
may end up negative, so keys like42:-123
and42:327375
look different but are produced from different sessions of the same data length.Consider:
- Use a stable digest (e.g. DJB2, MurmurHash) or
crypto.subtle.digest('SHA-1', data)
when available.- Fall back to
btoa(String.fromCharCode(...data))
for short payloads.- At minimum,
>>> 0
to keep the value unsigned.
421-428
: Guardcallback_sent
with a functional check
promise()
passesfalse
, socallback_sent()
may be a boolean.
A trivial typeof guard avoids accidentalfalse is not a function
mistakes if the signature changes upstream.-if (sendInfo.bytesSent === bufferOut.byteLength && callback_sent) { +if (sendInfo.bytesSent === bufferOut.byteLength && typeof callback_sent === "function") { callback_sent(); }
479-489
: O(N²) removal can hurt with large queues
indexOf
+splice
is fine for the 50-item cap, but if the limit is raised later it becomes quadratic.
Storing callbacks in aMap<requestKey, requestObj>
would give O(1) insert/remove/lookup, simplify duplicate handling, and eliminate linear scans elsewhere.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/js/msp.js
(2 hunks)
🔇 Additional comments (2)
src/js/msp.js (2)
433-438
: Timeout set-up looks good
_setupTimeout
is concise and centralises timer arming – nice.
439-477
: Retry handler: no back-off & relies onsendInfo.bytesSent
- A fixed 1 s retry interval can hammer sluggish links. Consider exponential back-off or
TIMEOUT * (1 + attempts)
to avoid congestion.- If the serial driver does not populate
bytesSent
,sendInfo.bytesSent === bufferOut.byteLength
evaluatesfalse
⇒ the request aborts immediately. Validate field presence or fall back to truthiness.- if (sendInfo.bytesSent === bufferOut.byteLength) { + if (!sendInfo || sendInfo.bytesSent === undefined || sendInfo.bytesSent === bufferOut.byteLength) {
- You re-assign
requestObj.timer
but never clear it inside_handleTimeout
.
Although the fired timer auto-invalidates, explicitlyclearTimeout(requestObj.timer)
before re-arming removes any doubt.
checking the coderrabbit "nitpicks"? i'm finding them useful outside of this project. |
bef646a
to
3433db1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/js/msp.js (1)
397-416
: Timeout handler: wrong variable in log, single re-send, no retry cap, and no cleanup on terminal failure.Issues:
- Uses this.timeout (removed) instead of this.TIMEOUT in the warning.
- Only a single re-send occurs; if that packet is also lost, the request can hang indefinitely.
- No retry cap; no error callback on terminal failure; stale entries remain in callbacks.
Fix all in place:
if (!requestExists) { obj.timer = setTimeout(() => { console.warn( - `MSP: data request timed-out: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} TIMEOUT: ${ - this.timeout - } QUEUE: ${this.callbacks.length} (${this.callbacks.map((e) => e.code)})`, + `MSP: data request timed-out: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} TIMEOUT: ${this.TIMEOUT} QUEUE: ${this.callbacks.length} (${this.callbacks.map((e) => e.code)})`, ); - serial.send(bufferOut, (_sendInfo) => { + // Increment attempts and enforce retry cap + obj.attempts = (obj.attempts || 0) + 1; + const MAX_RETRIES = 3; + if (obj.attempts > MAX_RETRIES) { + // Give up: remove from queue and notify caller (if desired) + clearTimeout(obj.timer); + this._removeRequestFromCallbacks?.(obj); + if (obj.callbackOnError && obj.callback) obj.callback({ error: "MSP timeout", code }); + return; + } + + serial.send(bufferOut, (_sendInfo) => { obj.stop = performance.now(); const executionTime = Math.round(obj.stop - obj.start); // We should probably give up connection if the request takes too long ? if (executionTime > 5000) { console.warn( `MSP: data request took too long: ${code} ID: ${serial.connectionId} TAB: ${GUI.active_tab} EXECUTION TIME: ${executionTime}ms`, ); } - - clearTimeout(obj.timer); // prevent leaks + // Re-arm timeout for the next retry window + clearTimeout(obj.timer); + obj.timer = setTimeout(() => { + // Recurse into the same handler + // NOTE: we rely on the outer closure's variables (obj, bufferOut) + // to drive further retries or terminal failure. + console.warn(`MSP: retrying (${obj.attempts}/${MAX_RETRIES}) for code ${code}`); + // Trigger the same resend path + serial.send(bufferOut, () => {}); + }, this.TIMEOUT); }); }, this.TIMEOUT); }If you prefer a cleaner shape, introduce a dedicated _handleTimeout(obj, bufferOut) and centralize removal logic in _removeRequestFromCallbacks (see below).
♻️ Duplicate comments (2)
src/js/msp.js (2)
385-385
: Duplicate detection by code only is too coarse; conflates distinct payloads and skews timers.Using only instance.code misclassifies concurrent requests with the same code but different payloads. It also means only the first one gets a timer; later ones will sit untracked if the reply never comes.
Apply this minimal diff to defer duplicate detection until bufferOut exists and compare code + payload bytes:
- const requestExists = this.callbacks.some((instance) => instance.code === code); + // Defer precise duplicate detection until after bufferOut is created + let requestExists = false; + let existing = null;…and immediately after bufferOut is constructed (below Line 387), insert:
+ // Precise duplicate detection: same code AND identical payload + const outView = new Uint8Array(bufferOut); + existing = this.callbacks.find((i) => { + if (i.code !== code) return false; + const iv = new Uint8Array(i.requestBuffer); + if (iv.byteLength !== outView.byteLength) return false; + for (let k = 0; k < iv.byteLength; k++) { + if (iv[k] !== outView[k]) return false; + } + return true; + }); + requestExists = Boolean(existing);
419-419
: Chain duplicate MSP request callbacks instead of pushing new onesTo avoid queuing multiple identical MSP requests (which leads to redundant serial sends and un-timed callbacks), refactor
send_message
so that duplicate calls attach their callbacks to the first outstanding request, and update the response handler to fire those chained callbacks.Key locations:
- src/js/msp.js (
send_message
, lines ~383–419): detect an existing request and only push a new entry when none exists; otherwise, append the newcallback_msp
toexisting.chained
.- src/js/msp/MSPHelper.js (around line 1768): after firing the primary callback, invoke any chained callbacks before removing the entry.
Suggested diff for src/js/msp.js:
--- a/src/js/msp.js +++ b/src/js/msp.js @@ send_message(code, data, callback_sent, callback_msp, doCallbackOnError) { - const requestExists = this.callbacks.some((instance) => instance.code === code); + // Check for an in-flight request with the same code + const existing = this.callbacks.find((instance) => instance.code === code); + const requestExists = !!existing; const bufferOut = code <= 254 ? this.encode_message_v1(code, data) : this.encode_message_v2(code, data); const obj = { code, requestBuffer: bufferOut, callback: callback_msp, callbackOnError: doCallbackOnError, start: performance.now(), }; - if (!requestExists) { - obj.timer = setTimeout(...); - } - - this.callbacks.push(obj); + if (!requestExists) { + // schedule timeout/retry only once + obj.timer = setTimeout(/* existing timeout handler */); + this.callbacks.push(obj); + } else if (callback_msp) { + // chain this callback onto the existing pending request + (existing.chained || (existing.chained = [])).push(callback_msp); + } // always send when there's payload or first request if (data || !requestExists) { serial.send(bufferOut, (sendInfo) => { if (sendInfo.bytesSent === bufferOut.byteLength && callback_sent) { callback_sent(); } }); }Suggested diff for src/js/msp/MSPHelper.js:
--- a/src/js/msp/MSPHelper.js +++ b/src/js/msp/MSPHelper.js @@ // inside the loop handling an MSP response (around line 1768) // fire primary callback if (!crcError || callbackOnError) { - if (callback) { - callback({ command: code, data, length: data.byteLength, crcError }); - } + if (callback) { + callback({ command: code, data, length: data.byteLength, crcError }); + } + // fire any chained callbacks for duplicate requests + const chained = dataHandler.callbacks[i].chained; + if (chained) { + chained.forEach((cb) => + cb({ command: code, data, length: data.byteLength, crcError }) + ); + } }Once these changes are in place, the primary request will handle both timeout/retries and cleanup, while all additional callers simply attach their callback to the same in-flight request.
🧹 Nitpick comments (18)
src/js/msp.js (5)
60-60
: Fixed TIMEOUT constant: good step; consider making it configurable per-transport/test.Hard-coding 1000ms is fine as a sane default. Consider allowing overrides via CONFIGURATOR or an optional param to send_message for test rigs and slow links.
378-383
: Early-return invokes callback_msp() without error context.Calling callback_msp() with no args conflates “not eligible to send” with “empty payload response.” Pass a sentinel (e.g., null or an Error-like object) so callers can discriminate.
- if (callback_msp) { - callback_msp(); - } + if (callback_msp) { + callback_msp({ error: "MSP send skipped: disconnected or virtual mode", code }); + }
278-282
: Leftover property set: messageIsJumboFrame is referenced but no longer tracked.This looks like a remnant. It’s not defined in MSP anymore and resetting it every dispatch serves no purpose.
- this.messageIsJumboFrame = false;
If you still need it, reintroduce a declared state property near Line 45.
67-67
: Typo in comment (“charactor”).- cli_buffer: [], // buffer for CLI charactor output + cli_buffer: [], // buffer for CLI character output
1-465
: Optional: add _removeRequestFromCallbacks helper to centralize timer cleanup.Centralizes clearTimeout + splice to avoid dangling timers. Call it from the retry-cap branch and the normal completion path.
// Add inside MSP object (near callbacks_cleanup) _removeRequestFromCallbacks(requestObj) { const idx = this.callbacks.indexOf(requestObj); if (idx > -1) { try { clearTimeout(this.callbacks[idx].timer); } catch (e) {} this.callbacks.splice(idx, 1); } },src/js/msp/debug/MSP_DEBUG_README.md (2)
317-330
: Add a language hint to the fenced “File Structure” block to satisfy MD040.-``` +```text src/js/msp/ ├── MSPCodes.js ├── MSPConnector.js ├── MSPHelper.js └── debug/ ├── msp_queue_monitor.js # Core monitoring functionality ├── msp_stress_test.js # Stress testing framework ├── msp_debug_dashboard.js # Visual dashboard UI ├── msp_test_runner.js # Console command interface ├── msp_debug_tools.js # Integration and auto-loading └── MSP_DEBUG_README.md # Documentation--- `1-365`: **Minor wording polish (optional).** Static analysis flagged a lot of “There might be a mistake here” on headings because of emojis and bold markers. Content is clear; no action needed unless you want a perfectly clean linter run. </blockquote></details> <details> <summary>src/js/msp/debug/index.js (1)</summary><blockquote> `1-16`: **Barrel export looks good; watch for export name collisions.** export * from './msp_debug_tools.js' will be a no-op if that file doesn’t export names (only side effects). Keep it if you plan to export from there later; otherwise you can drop it. </blockquote></details> <details> <summary>src/js/msp/debug/msp_debug_tools.js (2)</summary><blockquote> `36-52`: **Guard for non-browser/test environments and avoid double import.** This file assumes window exists. In node/JSDOM tests that import it directly, window may be undefined. Also, you already import "./msp_queue_monitor.js" at the top; the dynamic import is redundant. ```diff -if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") { +if (typeof window !== "undefined" + && window.location + && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) { - // Import the monitor and start it with minimal logging - import("./msp_queue_monitor.js").then(({ mspQueueMonitor }) => { + // Use the already-loaded export + import("./msp_queue_monitor.js").then(({ mspQueueMonitor }) => { mspQueueMonitor.addListener((status) => { // Only log alerts and significant events const alerts = Object.values(status.alerts).filter((a) => a); if (alerts.length > 0) { console.warn("🚨 MSP Alert detected - check dashboard for details"); } }); mspQueueMonitor.startMonitoring(2000); // Monitor every 2 seconds }); }
Optional: import { mspQueueMonitor } at the top instead of dynamic importing in dev.
12-34
: Console banner in production (optional).Consider gating the banner behind a dev flag to keep end-user consoles clean.
-console.log(` +if (typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1")) console.log(` … `);src/js/msp/debug/msp_debug_dashboard.js (2)
675-699
: Harden update path against missing MSP/mspQueueMonitor during early startup.If the dashboard is shown before MSP is initialized, mspQueueMonitor.getStatus() can throw. Wrap in try/catch and bail gracefully.
- status = status || mspQueueMonitor.getStatus(); + try { + status = status || mspQueueMonitor.getStatus(); + } catch (e) { + console.warn("MSP Debug Dashboard: status unavailable yet", e); + return; + }
794-806
: Avoid repeated DOM writes due to type mismatch in updateElement.textContent is string; value can be number. Coerce once to string for a stable equality check.
- if (element && element.textContent !== value) { - element.textContent = value; + if (element) { + const newText = String(value); + if (element.textContent !== newText) { + element.textContent = newText; + } }src/js/msp/debug/msp_queue_monitor.js (2)
447-456
: Timeout-rate deduction uses a possibly stale/undefinedmetrics.timeoutRate
; compute locally
_calculatePerformanceGrade()
should not rely onstartMonitoring/_collectMetrics()
having run. Compute the rate from raw counters to avoid undefined reads.Apply this diff:
- // Deduct for high timeout rate - if (this.metrics.timeoutRate > 0.1) { + // Deduct for high timeout rate + const timeoutRate = + this.metrics.totalRequests > 0 ? this.metrics.timeouts / this.metrics.totalRequests : 0; + if (timeoutRate > 0.1) { score -= 30; - } else if (this.metrics.timeoutRate > 0.05) { + } else if (timeoutRate > 0.05) { score -= 15; }
249-263
: Expose immutable, plain snapshots to listeners to prevent accidental mutation of internal state
getStatus()
shallow-copiesmetrics
/alerts
, but the Maps inside remain live references. Consider returning fully plain data (as in the report fix) or at least new Map instances to avoid accidental listener mutations.Happy to provide a small
_snapshot()
helper that returns a deep-plain snapshot for bothgetStatus()
andgenerateReport()
.src/js/msp/debug/msp_test_runner.js (3)
43-53
: Make periodic logging deterministic; avoid modulo-time race
if (Date.now() % 10000 < 500)
can log 0–2 times depending on timer jitter. Track last-log time instead.Apply this diff:
- // Log every 10 seconds if monitoring - if (Date.now() % 10000 < 500) { + // Log every ~10 seconds if monitoring (deterministic) + const now = Date.now(); + if (!this._lastQuickLogTs || now - this._lastQuickLogTs >= 10_000) { + this._lastQuickLogTs = now; console.log( `📊 MSP Status: Queue=${status.currentQueueSize}/${status.maxQueueSize}, Requests=${status.metrics.totalRequests}, AvgTime=${Math.round(status.metrics.avgResponseTime)}ms`, ); }And initialize the tracking field:
export const MSPTestRunner = { // Store the listener function so it can be removed later _quickMonitorListener: null, + _lastQuickLogTs: 0,
191-205
: Preserve Map metrics in JSON download using a custom replacerEven with the monitor fix, extra safety here ensures third-party objects serialize predictably.
Apply this diff:
- const blob = new Blob([JSON.stringify(report, null, 2)], { + const replacer = (_key, value) => { + if (value instanceof Map) return Object.fromEntries(value); + return value; + }; + const blob = new Blob([JSON.stringify(report, replacer, 2)], { type: "application/json", });
220-286
: Quick health check: tighten timing consistency and clean up monitor even on early returnsMinor robustness: use
performance.now()
for latency consistency and ensure the monitor is stopped on all paths.I can send a small patch converting the timing to
performance.now()
and using a singlefinally
to stop monitoring.src/js/msp/debug/msp_stress_test.js (1)
218-241
: Duplicate detection relies on error message text; prefer a structured indicatorCounting duplicates via
(err && err.message.includes("duplicate"))
is brittle. If the MSP layer doesn’t expose a code, consider exposing a boolean/enum on rejection (e.g.,{ code: 'DUPLICATE' }
) to avoid string matching.If updating MSP isn’t feasible here, at least lower-case and guard to reduce flakiness:
- const duplicateErrors = results.filter( - (r) => r.status === "rejected" || (r.value && r.value.error && r.value.error.includes("duplicate")), - ).length; + const duplicateErrors = results.filter((r) => { + if (r.status === "rejected") return true; + const msg = r.value && r.value.error; + return typeof msg === "string" && msg.toLowerCase().includes("duplicate"); + }).length;
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
src/js/main.js
(2 hunks)src/js/msp.js
(3 hunks)src/js/msp/debug/MSP_DEBUG_README.md
(1 hunks)src/js/msp/debug/index.js
(1 hunks)src/js/msp/debug/msp_debug_dashboard.js
(1 hunks)src/js/msp/debug/msp_debug_tools.js
(1 hunks)src/js/msp/debug/msp_queue_monitor.js
(1 hunks)src/js/msp/debug/msp_stress_test.js
(1 hunks)src/js/msp/debug/msp_test_runner.js
(1 hunks)src/js/utils/checkBrowserCompatibility.js
(1 hunks)test/js/msp/MSPHelper.test.js
(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- test/js/msp/MSPHelper.test.js
- src/js/utils/checkBrowserCompatibility.js
- src/js/main.js
🧰 Additional context used
🧬 Code graph analysis (6)
src/js/msp/debug/msp_debug_dashboard.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
mspQueueMonitor
(622-692)mspQueueMonitor
(622-692)src/js/msp/debug/msp_stress_test.js (2)
mspStressTest
(681-756)mspStressTest
(681-756)
src/js/msp/debug/msp_debug_tools.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
mspQueueMonitor
(622-692)mspQueueMonitor
(622-692)src/js/msp/debug/msp_test_runner.js (4)
status
(145-145)status
(245-245)status
(375-375)a
(197-197)
src/js/msp/debug/msp_queue_monitor.js (2)
src/js/msp/debug/msp_test_runner.js (6)
responseTime
(241-241)a
(197-197)analysis
(166-166)status
(145-145)status
(245-245)status
(375-375)src/js/msp/MSPHelper.js (2)
now
(2343-2343)callback
(1771-1771)
src/js/msp/debug/msp_stress_test.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
mspQueueMonitor
(622-692)mspQueueMonitor
(622-692)src/js/msp/debug/msp_test_runner.js (6)
results
(110-110)startTime
(107-107)startTime
(239-239)result
(84-84)result
(374-374)duration
(111-111)
src/js/msp/debug/msp_test_runner.js (3)
src/js/msp/debug/msp_queue_monitor.js (2)
mspQueueMonitor
(622-692)mspQueueMonitor
(622-692)src/js/msp/debug/msp_stress_test.js (2)
mspStressTest
(681-756)mspStressTest
(681-756)src/js/msp/debug/msp_debug_dashboard.js (2)
mspDebugDashboard
(1153-1153)mspDebugDashboard
(1153-1153)
src/js/msp.js (3)
src/js/serial.js (2)
serial
(319-319)serial
(319-319)src/js/data_storage.js (1)
CONFIGURATOR
(7-38)src/js/gui.js (1)
GUI
(483-483)
🪛 LanguageTool
src/js/msp/debug/MSP_DEBUG_README.md
[grammar] ~7-~7: There might be a mistake here.
Context: ...eatures 🔍 Real-time Queue Monitoring - Track queue size, response times, and su...
(QB_NEW_EN)
[grammar] ~8-~8: There might be a mistake here.
Context: ... size, response times, and success rates - Detect memory leaks and performance bott...
(QB_NEW_EN)
[grammar] ~10-~10: There might be a mistake here.
Context: ...ecks - Alert system for potential issues 🧪 Comprehensive Stress Testing - Qu...
(QB_NEW_EN)
[grammar] ~12-~12: There might be a mistake here.
Context: ...ssues 🧪 Comprehensive Stress Testing - Queue flooding tests - Timeout recovery ...
(QB_NEW_EN)
[grammar] ~13-~13: There might be a mistake here.
Context: ... Stress Testing** - Queue flooding tests - Timeout recovery validation - Memory lea...
(QB_NEW_EN)
[grammar] ~14-~14: There might be a mistake here.
Context: ...ding tests - Timeout recovery validation - Memory leak detection - Performance unde...
(QB_NEW_EN)
[grammar] ~15-~15: There might be a mistake here.
Context: ...overy validation - Memory leak detection - Performance under load testing 📊 **Vis...
(QB_NEW_EN)
[grammar] ~16-~16: There might be a mistake here.
Context: ...tection - Performance under load testing 📊 Visual Dashboard - Real-time metr...
(QB_NEW_EN)
[grammar] ~18-~18: There might be a mistake here.
Context: ...nder load testing 📊 Visual Dashboard - Real-time metrics display with smart upd...
(QB_NEW_EN)
[grammar] ~19-~19: There might be a mistake here.
Context: ...-time metrics display with smart updates - Live charts and graphs - Queue analysis ...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...h smart updates - Live charts and graphs - Queue analysis tools - Test result visua...
(QB_NEW_EN)
[grammar] ~21-~21: There might be a mistake here.
Context: ...charts and graphs - Queue analysis tools - Test result visualization - **Interactiv...
(QB_NEW_EN)
[grammar] ~22-~22: There might be a mistake here.
Context: ...alysis tools - Test result visualization - Interactive-friendly updates: Dashboar...
(QB_NEW_EN)
[grammar] ~23-~23: There might be a mistake here.
Context: ... pauses updates during user interactions - Clickable test results: Click on any t...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ...any test result for detailed information - Visual pause indicators when updates are...
(QB_NEW_EN)
[grammar] ~25-~25: There might be a mistake here.
Context: ...se indicators when updates are suspended ⚡ Easy-to-use API - Console commands...
(QB_NEW_EN)
[grammar] ~27-~27: There might be a mistake here.
Context: ...dates are suspended ⚡ Easy-to-use API - Console commands for quick testing - Pro...
(QB_NEW_EN)
[grammar] ~28-~28: There might be a mistake here.
Context: ...I** - Console commands for quick testing - Programmable test scenarios - Detailed r...
(QB_NEW_EN)
[grammar] ~29-~29: There might be a mistake here.
Context: ...ck testing - Programmable test scenarios - Detailed reporting and export ## Quick ...
(QB_NEW_EN)
[grammar] ~85-~85: There might be a mistake here.
Context: ...s The MSP Debug Tools provide two APIs: - MSPDebug: Modern, simplified API (reco...
(QB_NEW_EN)
[grammar] ~86-~86: There might be a mistake here.
Context: ...**: Modern, simplified API (recommended) - MSPTestRunner: Legacy API with additio...
(QB_NEW_EN)
[grammar] ~93-~93: There might be a mistake here.
Context: ...ring Commands | Command | Description | |---------|-------------| | `MSPDebug.st...
(QB_NEW_EN)
[grammar] ~94-~94: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.startMonitoring()
| Start m...
(QB_NEW_EN)
[grammar] ~95-~95: There might be a mistake here.
Context: ...| Start monitoring with console output | | MSPDebug.stopMonitoring()
| Stop mon...
(QB_NEW_EN)
[grammar] ~96-~96: There might be a mistake here.
Context: ...ug.stopMonitoring()| Stop monitoring | |
MSPDebug.getStatus()` | Get current M...
(QB_NEW_EN)
[grammar] ~97-~97: There might be a mistake here.
Context: ....getStatus()| Get current MSP status | |
MSPDebug.monitor.getStatus()` | Get c...
(QB_NEW_EN)
[grammar] ~98-~98: There might be a mistake here.
Context: ...| Get current MSP status (alternative) | | MSPDebug.analyze()
| Analyze current...
(QB_NEW_EN)
[grammar] ~103-~103: There might be a mistake here.
Context: ...ting Commands | Command | Description | |---------|-------------| | `MSPDebug.ru...
(QB_NEW_EN)
[grammar] ~104-~104: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.runTests()
| Run stress tes...
(QB_NEW_EN)
[grammar] ~105-~105: There might be a mistake here.
Context: ...ug.runTests()| Run stress test suite | |
MSPDebug.runFullSuite()` | Run comple...
(QB_NEW_EN)
[grammar] ~106-~106: There might be a mistake here.
Context: ...stress test suite with detailed output | | MSPDebug.runTest('test-name')
| Run ...
(QB_NEW_EN)
[grammar] ~107-~107: There might be a mistake here.
Context: ...-name')| Run a specific test by name | |
MSPDebug.quickHealthCheck()` | Run a ...
(QB_NEW_EN)
[grammar] ~108-~108: There might be a mistake here.
Context: ...heck()| Run a quick MSP health check | |
MSPDebug.stressScenario('scenario')` ...
(QB_NEW_EN)
[grammar] ~109-~109: There might be a mistake here.
Context: ...)| Run specific stress test scenario | |
MSPDebug.testAlerts()` | Test alert s...
(QB_NEW_EN)
[grammar] ~110-~110: There might be a mistake here.
Context: ...ebug.testAlerts()| Test alert system | |
MSPDebug.triggerTestAlerts()` | Manua...
(QB_NEW_EN)
[grammar] ~115-~115: There might be a mistake here.
Context: ...Alert Testing | Command | Description | |---------|-------------| | `MSPDebug.se...
(QB_NEW_EN)
[grammar] ~116-~116: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.setTestThresholds()
| Lower...
(QB_NEW_EN)
[grammar] ~117-~117: There might be a mistake here.
Context: ... | Lower thresholds for easier testing | | MSPDebug.setNormalThresholds()
| Res...
(QB_NEW_EN)
[grammar] ~118-~118: There might be a mistake here.
Context: ...esholds()| Restore normal thresholds | |
MSPDebug.testAlerts()` | Complete ale...
(QB_NEW_EN)
[grammar] ~123-~123: There might be a mistake here.
Context: ... Visual Tools | Command | Description | |---------|-------------| | `MSPDebug.sh...
(QB_NEW_EN)
[grammar] ~124-~124: There might be a mistake here.
Context: ... Description | |---------|-------------| | MSPDebug.show()
| Show visual debug ...
(QB_NEW_EN)
[grammar] ~125-~125: There might be a mistake here.
Context: ....show()| Show visual debug dashboard | |
MSPDebug.report()` | Generate and dow...
(QB_NEW_EN)
[grammar] ~142-~142: There might be a mistake here.
Context: ...ation including: - Full error messages - Performance metrics - JSON response data - Test...
(QB_NEW_EN)
[grammar] ~164-~164: There might be a mistake here.
Context: ...e limits with many simultaneous requests 2. rapid-fire - Tests high-frequency requ...
(QB_NEW_EN)
[grammar] ~165-~165: There might be a mistake here.
Context: ... - Tests high-frequency request handling 3. duplicates - Validates duplicate reque...
(QB_NEW_EN)
[grammar] ~166-~166: There might be a mistake here.
Context: ...- Validates duplicate request management 4. timeout-recovery - Tests timeout and r...
(QB_NEW_EN)
[grammar] ~167-~167: There might be a mistake here.
Context: ...y** - Tests timeout and retry mechanisms 5. memory-leaks - Checks for proper clean...
(QB_NEW_EN)
[grammar] ~168-~168: There might be a mistake here.
Context: ...for proper cleanup of completed requests 6. concurrent-mixed - Tests various reque...
(QB_NEW_EN)
[grammar] ~169-~169: There might be a mistake here.
Context: ...sts various request types simultaneously 7. queue-overflow - Tests behavior when q...
(QB_NEW_EN)
[grammar] ~170-~170: There might be a mistake here.
Context: ...sts behavior when queue reaches capacity 8. connection-disruption - Simulates conn...
(QB_NEW_EN)
[grammar] ~171-~171: There might be a mistake here.
Context: ...sruption** - Simulates connection issues 9. performance-load - Tests sustained loa...
(QB_NEW_EN)
[grammar] ~176-~176: There might be a mistake here.
Context: ...quency requests every 10ms for 5 seconds - queue-overflow - Floods queue beyond c...
(QB_NEW_EN)
[grammar] ~177-~177: There might be a mistake here.
Context: ...verflow** - Floods queue beyond capacity - mixed-load - Various request types and...
(QB_NEW_EN)
[grammar] ~188-~188: There might be a mistake here.
Context: ...ze**: Current number of pending requests - Response Times: Average, minimum, and ...
(QB_NEW_EN)
[grammar] ~189-~189: There might be a mistake here.
Context: ...age, minimum, and maximum response times - Success Rate: Percentage of successful...
(QB_NEW_EN)
[grammar] ~190-~190: There might be a mistake here.
Context: ...ate**: Percentage of successful requests - Timeout Rate: Percentage of requests t...
(QB_NEW_EN)
[grammar] ~191-~191: There might be a mistake here.
Context: ...e**: Percentage of requests that timeout - Request Distribution: Breakdown by MSP...
(QB_NEW_EN)
[grammar] ~192-~192: There might be a mistake here.
Context: ...bution**: Breakdown by MSP command codes - Error Tracking: Categorized error type...
(QB_NEW_EN)
[grammar] ~199-~199: There might be a mistake here.
Context: ...Queue Full**: Queue approaching capacity - ⏱️ High Timeout Rate: Excessive requ...
(QB_NEW_EN)
[grammar] ~200-~200: There might be a mistake here.
Context: ...meout Rate**: Excessive request timeouts - 🐌 Slow Responses: Average response ...
(QB_NEW_EN)
[grammar] ~201-~201: There might be a mistake here.
Context: ...ponses**: Average response time too high - 💾 Memory Leak: Callbacks not being ...
(QB_NEW_EN)
[grammar] ~202-~202: There might be a mistake here.
Context: ... Callbacks not being cleaned up properly ## Dashboard Features The visual dashboard...
(QB_NEW_EN)
[grammar] ~208-~208: There might be a mistake here.
Context: ...tatus**: Current queue state and metrics - Live Charts: Queue size and response t...
(QB_NEW_EN)
[grammar] ~209-~209: There might be a mistake here.
Context: ...s**: Queue size and response time trends - Queue Analysis: Detailed breakdown of ...
(QB_NEW_EN)
[grammar] ~210-~210: There might be a mistake here.
Context: ...: Detailed breakdown of pending requests - Alert Display: Active alerts and warni...
(QB_NEW_EN)
[grammar] ~211-~211: There might be a mistake here.
Context: ...rt Display**: Active alerts and warnings - Test Integration: Run tests directly f...
(QB_NEW_EN)
[grammar] ~212-~212: There might be a mistake here.
Context: ...ration**: Run tests directly from the UI - Export Tools: Generate and download re...
(QB_NEW_EN)
[grammar] ~290-~290: There might be a mistake here.
Context: ...## ✅ Current Features #### Alert System - Enhanced debug logging with reduced cons...
(QB_NEW_EN)
[grammar] ~291-~291: There might be a mistake here.
Context: ...debug logging with reduced console noise - Test infrastructure: `triggerTestAlerts(...
(QB_NEW_EN)
[grammar] ~292-~292: There might be a mistake here.
Context: ...erTestAlerts(),
setTestThresholds(),
setNormalThresholds()` - Visual alert display in dashboard - Smar...
(QB_NEW_EN)
[grammar] ~293-~293: There might be a mistake here.
Context: ...s()` - Visual alert display in dashboard - Smart threshold management for testing ...
(QB_NEW_EN)
[grammar] ~296-~296: There might be a mistake here.
Context: ... for testing #### Interactive Dashboard - Smart update pausing during user interac...
(QB_NEW_EN)
[grammar] ~297-~297: There might be a mistake here.
Context: ... update pausing during user interactions - Clickable test results with detailed inf...
(QB_NEW_EN)
[grammar] ~298-~298: There might be a mistake here.
Context: ...e test results with detailed information - Enhanced interaction handling for all UI...
(QB_NEW_EN)
[grammar] ~299-~299: There might be a mistake here.
Context: ...interaction handling for all UI elements - Visual feedback with updates pause indic...
(QB_NEW_EN)
[grammar] ~302-~302: There might be a mistake here.
Context: ...dates pause indicator #### Complete API - Dual API support: MSPDebug
(modern) an...
(QB_NEW_EN)
[grammar] ~303-~303: There might be a mistake here.
Context: ...g(modern) and
MSPTestRunner` (legacy) - All documented commands implemented and ...
(QB_NEW_EN)
[grammar] ~304-~304: There might be a mistake here.
Context: ...mented commands implemented and verified - Comprehensive testing methods (9 test ty...
(QB_NEW_EN)
[grammar] ~305-~305: There might be a mistake here.
Context: ...hods (9 test types + 3 stress scenarios) - Real-time monitoring with alert detectio...
(QB_NEW_EN)
[grammar] ~308-~308: There might be a mistake here.
Context: ... alert detection ### ✅ Verified Working - Alert system triggers correctly when thr...
(QB_NEW_EN)
[grammar] ~313-~313: There might be a mistake here.
Context: ...tion as documented - Auto-loading works in development environment ## File Struct...
(QB_NEW_EN)
[grammar] ~333-~333: There might be a mistake here.
Context: ...- Modern browser with ES6 module support - Access to the global MSP
object - Cons...
(QB_NEW_EN)
[grammar] ~334-~334: There might be a mistake here.
Context: ...port - Access to the global MSP
object - Console access for command-line interfac...
(QB_NEW_EN)
[grammar] ~339-~339: There might be a mistake here.
Context: ...## Troubleshooting Tools not loading: - Ensure MSP object is available globally ...
(QB_NEW_EN)
[grammar] ~343-~343: There might be a mistake here.
Context: ...rt errors Tests failing unexpectedly: - Verify serial connection is active - Che...
(QB_NEW_EN)
[grammar] ~348-~348: There might be a mistake here.
Context: ...ror messages Dashboard not appearing: - Try MSPTestRunner.showDashboard()
from...
(QB_NEW_EN)
[grammar] ~349-~349: There might be a mistake here.
Context: ...* - Try MSPTestRunner.showDashboard()
from console - Check for CSS conflicts - Ver...
(QB_NEW_EN)
[grammar] ~358-~358: There might be a mistake here.
Context: ...estclass 2. Update monitor metrics in
MSPQueueMonitor` 3. Extend dashboard UI as needed 4. Update ...
(QB_NEW_EN)
[grammar] ~364-~364: There might be a mistake here.
Context: ...te this documentation ## License Same as Betaflight Configurator project.
(QB_NEW_EN)
[grammar] ~364-~364: There might be a mistake here.
Context: ...Same as Betaflight Configurator project.
(QB_NEW_EN)
🪛 markdownlint-cli2 (0.17.2)
src/js/msp/debug/MSP_DEBUG_README.md
317-317: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔇 Additional comments (11)
src/js/msp.js (3)
390-395
: Request envelope looks fine.Capturing start with performance.now() is appropriate for timing instrumentation.
424-426
: Sent-callback gating is correct.Guarding callback_sent on bytesSent === bufferOut.byteLength prevents false positives on partial writes.
1-465
: Sanity check complete – no unintended side-effects detected
- No remaining references to the removed MIN_TIMEOUT or MAX_TIMEOUT fields; all other “timeout” occurrences are unrelated to adaptive timeouts.
messageIsJumboFrame
is only ever reset in src/js/msp.js (lines 278–281) and is not used elsewhere.- Callbacks stored on MSP.callbacks are invoked and cleaned up in src/js/msp/MSPHelper.js (around line 1772), so there’s no missing callback resolution logic in msp.js.
All changes look self-contained; no further action required.
src/js/msp/debug/msp_debug_dashboard.js (3)
883-989
: Chart rendering is efficient and Hi-DPI aware.Nice use of requestAnimationFrame and devicePixelRatio scaling; the <2 points guard prevents div-by-zero in x interpolation.
811-837
: Alert text is escaped; good.Mapping alert keys to user-facing messages while escaping fallback keeps XSS out of innerHTML.
853-879
: Queue contents rendering is stable and escaped.Fixed-slot rendering prevents layout shift; values are escaped; timer presence signal is a nice touch.
src/js/msp/debug/msp_queue_monitor.js (1)
595-617
: Ensure safe teardown when other code may have wrapped MSP methodsYou restore originals and clear the instrumentation flag—good. If other tooling wraps MSP after the monitor, restoring to our
original*
could clobber their overrides. If that’s a supported scenario, consider checking identity before restoration or offering aforce
flag.Do we ever allow other wrappers on
send_message/_dispatch_message
concurrently in the Configurator? If yes, I can propose a defensive unwrap sequence.src/js/msp/debug/msp_test_runner.js (1)
434-438
: Global side effect is fine for dev toolingExposing
window.MSPTestRunner
is deliberate here and helps discovery. LGTM.src/js/msp/debug/msp_stress_test.js (3)
527-549
: Report generation composes with monitor report nicelySolid aggregation. Once the monitor’s Map serialization fix lands, this will produce accurate artifacts.
670-676
: Graceful cleanup—goodStopping the shared monitor without destroying the singleton is the right trade-off here.
247-281
: I’ve requested the surrounding context for howTIMEOUT
is declared insrc/js/msp.js
so we can confirm whether it’s a module-level constant or an instance property. Once we have that snippet, we can determine if mutatingthis.msp.TIMEOUT
has any effect and adjust the test accordingly.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
Preview URL: https://935e385c.betaflight-configurator.pages.dev |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/js/msp/debug/msp_queue_monitor.js (2)
270-284
: ConvertMap
s to plain objects in status/report to make JSON exports accurate.
requestsByCode
anderrorsByType
areMap
s; JSON will serialise them as{}
. This was previously flagged.Apply this diff:
@@ - metrics: { ...this.metrics }, + metrics: this._toPlainMetrics(), @@ - rawMetrics: this.metrics, + rawMetrics: this._toPlainMetrics(),Add this helper inside the class:
_toPlainMetrics() { const { requestsByCode, errorsByType, responseTimes, ...rest } = this.metrics; return { ...rest, responseTimes: Array.isArray(responseTimes) ? [...responseTimes] : [], requestsByCode: Object.fromEntries(requestsByCode), errorsByType: Object.fromEntries(errorsByType), }; }Also applies to: 432-446
256-256
: Guard allcallbacks
accesses to prevent runtime TypeError when MSP is uninitialised or torn down.There are still direct
.length
/.map
/iteration usages that can throw ifthis.msp.callbacks
is undefined or not an array. Prior feedback already called this out.Apply this diff:
@@ - this.currentQueueSize = this.msp.callbacks.length; + this.currentQueueSize = this.msp.callbacks?.length ?? 0; @@ - currentQueueSize: this.msp.callbacks.length, + currentQueueSize: this.msp.callbacks?.length ?? 0, @@ - metrics: { ...this.metrics }, + metrics: this._toPlainMetrics(), @@ - queueContents: this.msp.callbacks.map((req) => ({ + queueContents: (this.msp.callbacks || []).map((req) => ({ code: req.code, attempts: req.attempts || 0, - age: performance.now() - req.start, + age: req?.start ? performance.now() - req.start : 0, hasTimer: !!req.timer, })), @@ - const callbacks = this.msp.callbacks; + const callbacks = this.msp.callbacks || [];And inside the analysis loop:
@@ - // Age analysis - const age = now - req.start; + // Age analysis (guard missing start) + const start = typeof req.start === "number" ? req.start : now; + const age = now - start;Also applies to: 273-283, 290-352
🧹 Nitpick comments (8)
src/js/msp/debug/msp_queue_monitor.js (8)
94-114
: Make duplicate detection data-type safe (TypedArray/ArrayBuffer/Array) and simpler to read.The current element-by-element loop assumes
data
has.length
and index access. MSP payloads can be TypedArrays or ArrayBuffers. Centralise equality in a helper to avoid false negatives.Apply this diff:
- // Count potential duplicates already present in the queue (same code + same data) + // Count potential duplicates already present in the queue (same code + same data) try { - const callbacks = this.msp.callbacks || []; - const isDuplicate = callbacks.some((req) => { - if (req.code !== code) return false; - const a = data; - const b = req.data; - if (!a && !b) return true; - if (!a || !b) return false; - if (a === b) return true; - if (a.length !== b.length) return false; - for (let i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; - }); + const callbacks = this.msp.callbacks || []; + const isDuplicate = callbacks.some((req) => req.code === code && this._dataEquals(data, req.data)); if (isDuplicate) this.metrics.duplicates++; } catch (_) { // best-effort only }Add this helper inside the class (e.g., near other private helpers):
// Type-tolerant binary/array equality _dataEquals(a, b) { if (a === b) return true; if (!a || !b) return false; // ArrayBuffer or views const toU8 = (x) => { if (x instanceof ArrayBuffer) return new Uint8Array(x); if (ArrayBuffer.isView(x)) return new Uint8Array(x.buffer, x.byteOffset, x.byteLength); return null; }; const au8 = toU8(a); const bu8 = toU8(b); if (au8 && bu8) { if (au8.byteLength !== bu8.byteLength) return false; for (let i = 0; i < au8.byteLength; i++) if (au8[i] !== bu8[i]) return false; return true; } // Arrays if (Array.isArray(a) && Array.isArray(b)) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; } // Fallback for primitives/strings/objects try { return JSON.stringify(a) === JSON.stringify(b); } catch { return false; } }
220-233
: Emit an immediate snapshot on start to avoid a 1s blind spot.Apply this diff:
this.isMonitoring = true; + // capture a snapshot immediately + this._collectMetrics(); + this._notifyListeners(); this.monitoringInterval = setInterval(() => { this._collectMetrics(); this._notifyListeners(); }, intervalMs);
10-22
: Stabilise the metrics shape by initialisingsuccessRate
andtimeoutRate
to0
.These keys are used elsewhere (e.g., grading) but are only populated by
_collectMetrics()
. Initialising prevents undefined reads before the first interval tick and afterresetMetrics()
.Apply this diff:
@@ this.metrics = { totalRequests: 0, completedRequests: 0, failedRequests: 0, timeouts: 0, duplicates: 0, avgResponseTime: 0, maxResponseTime: 0, queuePeakSize: 0, requestsByCode: new Map(), responseTimes: [], errorsByType: new Map(), + successRate: 0, + timeoutRate: 0, }; @@ resetMetrics() { this.metrics = { totalRequests: 0, completedRequests: 0, failedRequests: 0, timeouts: 0, duplicates: 0, avgResponseTime: 0, maxResponseTime: 0, queuePeakSize: 0, requestsByCode: new Map(), responseTimes: [], errorsByType: new Map(), + successRate: 0, + timeoutRate: 0, };Also applies to: 389-401
468-499
: Make grading independent of the timer tick by deriving rates locally.Avoid relying on
this.metrics.timeoutRate
being refreshed by_collectMetrics()
.Apply this diff:
_calculatePerformanceGrade() { let score = 100; - // Deduct for high timeout rate - if (this.metrics.timeoutRate > 0.1) { + // Deduct for high timeout rate + const timeoutRate = + this.metrics.totalRequests > 0 ? this.metrics.timeouts / this.metrics.totalRequests : 0; + if (timeoutRate > 0.1) { score -= 30; - } else if (this.metrics.timeoutRate > 0.05) { + } else if (timeoutRate > 0.05) { score -= 15; } @@ - if (this.metrics.avgResponseTime > 2000) { + const avg = this.metrics.avgResponseTime || 0; + if (avg > 2000) { score -= 25; - } else if (this.metrics.avgResponseTime > 1000) { + } else if (avg > 1000) { score -= 10; } @@ - const failureRate = - this.metrics.totalRequests > 0 ? this.metrics.failedRequests / this.metrics.totalRequests : 0; + const failureRate = + this.metrics.totalRequests > 0 ? this.metrics.failedRequests / this.metrics.totalRequests : 0;
357-369
: Return an unsubscribe fromaddListener
for ergonomics.Apply this diff:
addListener(callback) { - this.listeners.push(callback); + this.listeners.push(callback); + return () => this.removeListener(callback); }
696-698
: Avoid instantiating just to destroy; guard the proxydestroy()
.Calling
mspQueueMonitor.destroy()
when no instance exists will currently attempt to construct and then destroy. Early-return instead.Apply this diff:
- destroy(...args) { - return this.instance.destroy(...args); - }, + destroy(...args) { + if (!_mspQueueMonitorInstance) return; + return _mspQueueMonitorInstance.destroy(...args); + },
179-215
: Rename “memory leak” alert or refine detection to avoid false positives.Queue length spikes indicate backlog, not necessarily leaks. Either rename to “backlog” or track monotonic growth over a window (e.g., > N samples increasing).
If you’d like, I can add a rolling-window slope detector and rename the flag to
backlog
while keeping a separatememoryLeakSuspected
derived from >X minutes of continuous growth.
217-233
: Minor: rate/average recomputation could be incremental and cheaper.The rolling average currently reduces the entire window on each completion. For a 100-sample cap this is fine; if you increase it later, consider keeping a running sum.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/js/msp/debug/msp_queue_monitor.js
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/msp/debug/msp_queue_monitor.js (2)
src/js/msp/debug/msp_test_runner.js (6)
a
(197-197)responseTime
(241-241)analysis
(166-166)status
(145-145)status
(245-245)status
(375-375)src/js/msp/MSPHelper.js (2)
now
(2343-2343)callback
(1771-1771)
🔇 Additional comments (2)
src/js/msp/debug/msp_queue_monitor.js (2)
55-82
: Instrumentation is correctly bound and guarded against double-patching.Using bound originals and an instrumentation flag avoids context loss and re-entry issues. Looks good.
640-654
: Proxy initialisation pattern is sound.Lazy initialisation via
window.MSP
with an informative error is appropriate for the debug-only tooling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/js/msp/debug/msp_stress_test.js (2)
666-673
: Fix: runSpecificTest hardcodes "PASSED"; derive status from the actual resultThis still reports "PASSED" regardless of failures returned by the test method. Reuse the status derivation helper.
Apply this diff:
try { const result = await testMethod(); return { name: testName, - status: "PASSED", + status: this.getTestStatus(testName, result), result, metrics: this.monitor.getStatus(), };
393-442
: Connection Disruption Test Must Toggle Module-Level Serial InstanceThe stress-test’s testConnectionDisruption currently flips this.msp.serial.connected, but msp.send_message()/promise() checks the singleton serial imported in msp.js (
import { serial } from "./serial.js"
). Mutating this.msp.serial has no effect on the branch in send_message, so the “disconnected” path is never exercised.• File: src/js/msp/debug/msp_stress_test.js
• Method: testConnectionDisruptionApply these changes:
- Import the real serial singleton from serial.js
- Replace all this.msp.serial references with moduleSerial
--- a/src/js/msp/debug/msp_stress_test.js +++ b/src/js/msp/debug/msp_stress_test.js @@ 1,5c1,7 -import { mspQueueMonitor } from "./msp_queue_monitor.js"; +import { mspQueueMonitor } from "./msp_queue_monitor.js"; +// Use the same serial instance that MSP methods reference +import { serial as moduleSerial } from "../../serial.js"; export class MSPStressTest { @@ 393,7c395,7 - const originalConnected = this.msp.serial?.connected; + const originalConnected = moduleSerial.connected; try { - if (this.msp.serial) { - this.msp.serial.connected = false; + moduleSerial.connected = false; } // Try to send requests while "disconnected" @@ 414,7c416,7 - if (this.msp.serial) { - this.msp.serial.connected = originalConnected; + moduleSerial.connected = originalConnected; } // Test recovery @@ 422,7c424,7 - } finally { - // Ensure connection is restored - if (this.msp.serial) { - this.msp.serial.connected = originalConnected; + } finally { + // Ensure connection is restored even on error + moduleSerial.connected = originalConnected; }This ensures you’re toggling the exact
serial.connected
flag that send_message checks.
🧹 Nitpick comments (4)
src/js/msp/debug/msp_stress_test.js (4)
248-260
: Duplicate test metrics: distinguish duplicate rejections from other failuresCounting every rejection as a "duplicate" can mask unrelated errors. Separate them so the status helper can be precise.
- const duplicateErrors = results.filter( - (r) => r.status === "rejected" || (r.value && r.value.error && r.value.error.includes("duplicate")), - ).length; + const isDupErr = (val) => typeof val?.error === "string" && /duplicat(e|ion)/i.test(val.error); + const duplicateErrors = results.filter( + (r) => r.status === "rejected" || isDupErr(r.value) + ).length; + const otherFailures = results.filter( + (r) => r.status === "rejected" || (!!r.value && !!r.value.error) + ).length - duplicateErrors; return { duplicatesSent: duplicateCount, successful, duplicateRejections: duplicateErrors, + otherFailures, queueSizeAfter: this.msp.callbacks.length, };
268-299
: Timeout Recovery: verify the error is actually a timeout before claiming successAny rejection is treated as a timeout. Tighten detection to avoid false positives and improve diagnostics.
- try { - await this.msp.promise(code, null); - return { error: "Expected timeout but request succeeded" }; - } catch (error) { + try { + await this.msp.promise(code, null); + return { error: "Expected timeout but request succeeded" }; + } catch (error) { const timeoutTime = performance.now() - startTime; // Test that new requests work after timeout this.msp.TIMEOUT = originalTimeout; await this.wait(200); const recoveryStart = performance.now(); await this.msp.promise(this.testCodes.MSP_IDENT, null); const recoveryTime = performance.now() - recoveryStart; return { - timeoutOccurred: true, + timeoutOccurred: /timeout/i.test(error?.message || "") || error?.name === "TimeoutError" || error?.code === "ETIMEDOUT", timeoutDuration: timeoutTime, recoveryTime, queueCleanedUp: this.msp.callbacks.length === 0, }; }Optionally add a helper to centralize timeout detection if used elsewhere.
367-388
: Queue Overflow: count overflow-specific rejections, not all errorsRight now any rejection increments overflow count, which can conflate unrelated failures. Filter by recognizable overflow messages to reduce false positives.
- const results = await Promise.allSettled(promises); - const rejected = results.filter((r) => r.status === "rejected" || r.value?.error).length; + const results = await Promise.allSettled(promises); + const isOverflow = (val) => + typeof val?.error === "string" && /(overflow|queue\s*full)/i.test(val.error); + const rejectedDueToOverflow = results.filter( + (r) => r.status === "rejected" || isOverflow(r.value) + ).length; return { attemptedRequests: overflowCount, maxQueueSize: maxQueue, - rejectedDueToOverflow: rejected, - overflowHandled: rejected > 0, + rejectedDueToOverflow, + overflowHandled: rejectedDueToOverflow > 0, finalQueueSize: this.msp.callbacks.length, };
545-567
: Report: include a guard for empty results to avoid division-by-zero and NaN successRateNot likely here, but runSpecificTest consumers might call generateTestReport([]). Cheap guard improves resilience.
- const totalTests = results.length; + const totalTests = results.length || 0; const passedTests = results.filter((r) => r.status === "PASSED").length; - const failedTests = totalTests - passedTests; + const failedTests = Math.max(0, totalTests - passedTests); @@ - successRate: passedTests / totalTests, + successRate: totalTests ? passedTests / totalTests : 0,
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/js/msp/debug/msp_stress_test.js
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/js/msp/debug/msp_stress_test.js (2)
src/js/msp/debug/msp_queue_monitor.js (2)
mspQueueMonitor
(643-713)mspQueueMonitor
(643-713)src/js/msp/debug/msp_test_runner.js (4)
result
(84-84)result
(374-374)results
(110-110)report
(185-185)
Summary by CodeRabbit
New Features
Bug Fixes
Refactor
Documentation
Tests